﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using SharedGenomics.Workbench.Core;
using SharedGenomics.Workbench.Tests.TestObjects;

using Rhino.Mocks;

namespace SharedGenomics.Workbench.Tests.Core
{
    [TestClass]
    public class EventBrokerTests
    {
        private MockRepository mocks;

        [TestInitialize]
        public void Setup()
        {
            this.mocks = new MockRepository();
        }

        [TestMethod]
        public void SubscriberShouldRecieveMessage()
        {
            EventBroker eventBroker = new EventBroker();
            IEventSubscriber<object> sub = this.mocks.DynamicMock<IEventSubscriber<object>>();

            using (this.mocks.Record())
            {
                sub.Expect(s => s.Receive(null)).IgnoreArguments();
            }

            using (this.mocks.Playback())
            {
                eventBroker.Subscribe(sub);
                eventBroker.Publish(new object());
            }
        }

        [TestMethod]
        public void SubscriberShouldReceiveAllMessages()
        {
            EventBroker eventBroker = new EventBroker();
            var sub = new MultiEventSubscriber();
            eventBroker.SubscribeAll(sub);

            eventBroker.Publish(new object());
            eventBroker.Publish("abc");

            Assert.IsNotNull(sub.LastObjectMessage);
            Assert.AreEqual("abc", sub.LastStringMessage);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void SubscribeShouldThrowIfArgumentNull()
        {
            EventBroker eventBroker = new EventBroker();
            eventBroker.Subscribe<object>(null);
        }

        [TestMethod]
        public void SubscriberShouldNotBeSentMessageIfCollected()
        {
            MockEventBroker broker = new MockEventBroker();
            WeakReference subRef = this.mocks.Stub<WeakReference>(new object());
            broker.GetReferencesForEventFunc = () => new List<WeakReference> { subRef };

            using (this.mocks.Record())
            {
                subRef.Stub(wr => wr.IsAlive).Return(false);
            }

            using (this.mocks.Playback())
            {
                broker.Publish<object>(new object());
            }
        }

        [TestMethod]
        public void SubscriberShouldBeRemovedIfCollected()
        {
            MockEventBroker broker = new MockEventBroker();
            WeakReference subRef = this.mocks.Stub<WeakReference>(new object());
            List<WeakReference> refs = new List<WeakReference> { subRef };
            broker.GetReferencesForEventFunc = () => refs;

            using (this.mocks.Record())
            {
                subRef.Stub(wr => wr.IsAlive).Return(false);
            }

            using (this.mocks.Playback())
            {
                broker.Publish<object>(new object());
            }

            Assert.AreEqual(0, refs.Count);
        }

        [TestMethod]
        public void ShouldNotSendMessageIfUnsubscribed()
        {
            EventBroker eventBroker = new EventBroker();
            IEventSubscriber<object> sub = this.mocks.StrictMock<IEventSubscriber<object>>();

            this.mocks.ReplayAll();

            eventBroker.Subscribe(sub);
            eventBroker.Unsubscribe(sub);
            eventBroker.Publish(new object());
        }

        [TestMethod]
        public void ShouldUnsubscribeGivenEventOnly()
        {
            EventBroker eventBroker = new EventBroker();
            MultiEventSubscriber sub = new MultiEventSubscriber();

            eventBroker.Subscribe<object>(sub);
            eventBroker.Subscribe<string>(sub);
            eventBroker.Unsubscribe<object>(sub);

            eventBroker.Publish(new object());
            eventBroker.Publish("abc");

            //subscriber should have received the string message only
            Assert.IsNull(sub.LastObjectMessage);
            Assert.AreEqual("abc", sub.LastStringMessage);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void UnsubscribeShouldThrowIfArgumentNull()
        {
            EventBroker eventBroker = new EventBroker();
            eventBroker.Unsubscribe<object>(null);
        }

        [TestMethod]
        public void ShouldUnsubscribeAllEvents()
        {
            EventBroker eventBroker = new EventBroker();
            MultiEventSubscriber sub = new MultiEventSubscriber();

            eventBroker.Subscribe<object>(sub);
            eventBroker.Subscribe<string>(sub);
            eventBroker.UnsubscribeAll(sub);

            eventBroker.Publish(new object());
            eventBroker.Publish("abc");

            //subscriber should not receive either message
            Assert.IsNull(sub.LastObjectMessage);
            Assert.IsNull(sub.LastStringMessage);
        }

        [TestMethod]
        public void ShouldSubscribeOnceOnly()
        {
            MockEventBroker broker = new MockEventBroker();
            IEventSubscriber<object> sub = this.mocks.Stub<IEventSubscriber<object>>();
            ICollection<WeakReference> eventRefs = new List<WeakReference> { new WeakReference(sub, false) };
            broker.GetReferencesForEventFunc = () => eventRefs;

            this.mocks.ReplayAll();

            broker.Subscribe(sub);
            Assert.AreEqual(1, eventRefs.Count);
        }

        #region MockEventBroker

        private class MockEventBroker : EventBroker
        {
            public Func<ICollection<WeakReference>> GetReferencesForEventFunc { get; set; }

            protected override ICollection<WeakReference> GetSubscribersForEvent(Type eventType)
            {
                return this.GetReferencesForEventFunc();
            }
        }

        #endregion
    }
}
